import 'dart:io';

import 'package:dbio_utils/generic_filehandle/local_file.dart';
import 'package:dbio_utils/tabix/csi.dart';
import 'package:dbio_utils/tabix/tabix_indexed_file.dart';
import 'package:dbio_utils/tabix/virtual_offset.dart';
import 'package:flutter_test/flutter_test.dart';

class RecordCollector {
  List records = [];

  int get length => records.length;

  void callback(line, fileOffset) {
    records.add({
      'line': line,
      'offset': fileOffset,
    });
  }

  RecordCollector();

  clear() {
    records.clear();
  }

  text() => records.map((e) => '${e['line']}\n').join('');

  expectNoDuplicates() {
    Map seen = {};
    records.forEach((item) {
      expect(null, seen[item['line']]);
      seen[item['line']] = item['offset'];
    });
  }
}

void main() {
  test('can read ctgA:1000..4000', () async {
    var file = LocalFile(File('test/tabix/data/volvox.test.vcf.gz'));
    var tbi = LocalFile(File('test/tabix/data/volvox.test.vcf.gz.tbi'));
    final f = TabixIndexedFile(
      fileHandle: file,
      tbiFileHandle: tbi,
      yieldLimit: 10,
      renameRefSeq: (s) => s.replaceFirst('contig', 'ctg'),
    );
    var items = RecordCollector();
    await f.getLines(refName: 'ctgA', start: 1000, end: 4000, callback: items.callback);
    items.expectNoDuplicates();
    expect(8, items.length);

    items.records.forEach((e) {
      String _line = e['line'];
      int offset = e['offset'];
      var line = _line.split('\t');
      expect(line[0], 'contigA');
      expect(int.tryParse(line[1]), greaterThan(999));
      expect(int.tryParse(line[1]), lessThan(4001));
      expect(offset, greaterThanOrEqualTo(0));
    });

    items.clear();
    await f.getLines(refName: 'ctgA', start: 3000, end: 3000, callback: items.callback);
    expect(items.length, equals(0));
    items.clear();
    await f.getLines(refName: 'ctgA', start: 2999, end: 3000, callback: items.callback);
    expect(items.length, equals(1));
    items.clear();
    await f.getLines(refName: 'ctgA', start: 3000, end: 3001, callback: items.callback);
    expect(items.length, equals(0)); //1

    var headerString = await f.getHeader({});
    expect(headerString.length, equals(10431));

    expect(headerString[headerString.length - 1], equals('\n'));

    expect(
        await f.getMetadata({}),
        equals({
          'columnNumbers': {'end': 0, 'ref': 1, 'start': 2},
          'coordinateType': '1-based-closed',
          'maxBlockSize': 1 << 16,
          'format': 'VCF',
          'metaChar': '#',
          'firstDataLine': new VirtualOffset(0, 10431),
          'refIdToName': ['ctgA'],
          'refNameToId': {'ctgA': 0},
          'skipLines': 0,
          'maxBinNumber': 37449,
          'maxRefLength': 536870912,
        }));
  });

  test('- can read ctgA:10000', () async {
    var file = LocalFile(File('test/tabix/data/volvox.test.vcf.gz'));
    var tbi = LocalFile(File('test/tabix/data/volvox.test.vcf.gz.tbi'));
    var f = TabixIndexedFile(
      fileHandle: file,
      tbiFileHandle: tbi,
      yieldLimit: 10,
      renameRefSeq: (s) => s.replaceFirst('contig', 'ctg'),
    );
    var items = new RecordCollector();
    await f.getLines(refName: 'ctgA', start: 10000, callback: items.callback);
    items.expectNoDuplicates();
    expect(items.length, equals(30));

    items.records.forEach((e) {
      String _line = e['line'];
      int offset = e['offset'];
      var line = _line.split('\t');
      expect(line[0], equals('contigA'));
      expect(int.tryParse(line[1]), greaterThan(9999));
      expect(offset, greaterThanOrEqualTo(0));
    });
  });

  test('can read hole ctgA', () async {
    var file = LocalFile(File('test/tabix/data/volvox.test.vcf.gz'));
    var tbi = LocalFile(File('test/tabix/data/volvox.test.vcf.gz.tbi'));
    var f = TabixIndexedFile(
      fileHandle: file,
      tbiFileHandle: tbi,
      yieldLimit: 10,
      // renameRefSeq: (s) => s.replaceFirst('contig', 'ctg'),
    );
    var items = new RecordCollector();
    await f.getLines(refName: 'contigA', callback: items.callback);
    items.expectNoDuplicates();
    expect(items.length, equals(109));

    items.records.forEach((e) {
      String _line = e['line'];
      int offset = e['offset'];
      var line = _line.split('\t');
      expect(line[0], equals('contigA'));
      expect(offset, greaterThanOrEqualTo(0));
    });
  });

  test('can count lines with TBI', () async {
    var file = LocalFile(File('test/tabix/data/volvox.test.vcf.gz'));
    var tbi = LocalFile(File('test/tabix/data/volvox.test.vcf.gz.tbi'));
    var f = TabixIndexedFile(
      fileHandle: file,
      tbiFileHandle: tbi,
      yieldLimit: 10,
    );
    expect(await f.lineCount('contigA'), equals(109));
    expect(await f.lineCount('nonexistent'), equals(-1));
  });

  test('can count lines with CSI', () async {
    var file = LocalFile(File('test/tabix/data/volvox.test.vcf.gz'));
    var csi = LocalFile(File('test/tabix/data/volvox.test.vcf.gz.csi'));
    var f = TabixIndexedFile(
      fileHandle: file,
      csiFileHandle: csi,
      yieldLimit: 10,
    );
    expect(await f.lineCount('contigA'), equals(109));
    expect(await f.lineCount('nonexistent'), equals(-1));
  });

  test('can not count lines without pseudo-bin', () async {
    var file = LocalFile(File('test/tabix/data/volvox.test.vcf.gz'));
    var tbi = LocalFile(File('test/tabix/data/volvox.test.vcf.gz.tbi.no_pseudo'));
    var f = TabixIndexedFile(
      fileHandle: file,
      tbiFileHandle: tbi,
      yieldLimit: 10,
    );
    expect(await f.lineCount('contigA'), equals(-1));
  });

  test('can query volvox.sort.gff3.gz.1', () async {
    var f = new TabixIndexedFile(
      fileHandle: LocalFile(File('test/tabix/data/volvox.sort.gff3.gz.1')),
      tbiFileHandle: LocalFile(File('test/tabix/data/volvox.sort.gff3.gz.tbi')),
    );

    var headerString = await f.getHeader({});
    expect(headerString[headerString.length - 1], equals('\n'));
    expect(headerString.length, equals(130));

    var lines = RecordCollector();
    await f.getLines(refName: 'ctgB', start: 0, callback: lines.callback);
    lines.expectNoDuplicates();
    expect(lines.length, equals(4));
    expect(lines.records[3]['line'], equals('ctgB	example	remark	4715	5968	.	-	.	Name=f05;Note=ああ、この機能は、世界中を旅しています！'));
    lines.clear();
    await f.getLines(refName: 'ctgA', start: 10000000, callback: lines.callback);
    expect(lines.length, equals(0));
    lines.clear();
    await f.getLines(refName: 'ctgA', start: 0, callback: lines.callback);
    expect(lines.length, equals(237));
    lines.clear();
    await f.getLines(refName: 'ctgB', start: 0, callback: lines.callback);
    expect(lines.length, equals(4));
    lines.clear();
    await f.getLines(refName: 'ctgB', start: 0, end: 4715, callback: lines.callback);
    expect(lines.length, equals(4));
    lines.clear();
    await f.getLines(refName: 'ctgB', start: 1, end: 4714, callback: lines.callback);
    expect(lines.length, equals(3));
  });

  test('can query gvcf.vcf.gz', () async {
    var f = TabixIndexedFile(
      fileHandle: LocalFile(File('test/tabix/data/gvcf.vcf.gz')),
      tbiFileHandle: LocalFile(File('test/tabix/data/gvcf.vcf.gz.tbi')),
    );

    var headerString = await f.getHeader();
    expect(headerString.length, equals(53));
    expect(headerString[headerString.length - 1], equals('\n'));

    var lines = [];

    void _callback(line, offset) {
      lines.add(line);
    }

    await f.getLines(refName: 'ctgB', start: 0, callback: _callback);
    expect(lines.length, equals(0));

    await f.getLines(refName: 'ctgA', start: -2, end: 3000, callback: _callback);
    expect(lines.length, equals(0));
    await f.getLines(refName: 'ctgA', start: -50, end: -20, callback: _callback);

    expect(lines.length, equals(0));
    await f.getLines(refName: 'ctgA', start: 4000, end: 5000, callback: _callback);

    expect(lines.length, equals(7));
    lines.clear();
    await f.getLines(refName: 'ctgA', start: 4383, end: 4384, callback: _callback);

    expect(lines.length, equals(1));
    lines.clear();
    await f.getLines(refName: 'ctgA', start: 4384, end: 4385, callback: _callback);

    expect(lines.length, equals(1));
    lines.clear();
    await f.getLines(refName: 'ctgA', start: 4385, end: 4386, callback: _callback);
    expect(lines.length, equals(1));
    lines.clear();
    await f.getLines(refName: 'ctgA', start: 4369, end: 4370, callback: _callback);

    expect(lines.length, equals(1));
  });
}
